[id].tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { usePrevious } from '@uidotdev/usehooks'
  2. import { useParams } from 'common/hooks/useParams'
  3. import Link from 'next/link'
  4. import { useRouter } from 'next/router'
  5. import { useEffect } from 'react'
  6. import { Button } from 'ui'
  7. import { Admonition } from 'ui-patterns'
  8. import { SQLEditor } from '@/components/interfaces/SQLEditor/SQLEditor'
  9. import { generateSnippetTitle } from '@/components/interfaces/SQLEditor/SQLEditor.constants'
  10. import DefaultLayout from '@/components/layouts/DefaultLayout'
  11. import { EditorBaseLayout } from '@/components/layouts/editors/EditorBaseLayout'
  12. import { useEditorType } from '@/components/layouts/editors/EditorsLayout.hooks'
  13. import SQLEditorLayout from '@/components/layouts/SQLEditorLayout/SQLEditorLayout'
  14. import { SQLEditorMenu } from '@/components/layouts/SQLEditorLayout/SQLEditorMenu'
  15. import { useContentIdQuery } from '@/data/content/content-id-query'
  16. import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
  17. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  18. import { IS_PLATFORM } from '@/lib/constants'
  19. import { SnippetWithContent, useSnippets, useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2'
  20. import { createTabId, useTabsStateSnapshot } from '@/state/tabs'
  21. import type { NextPageWithLayout } from '@/types'
  22. const SqlEditor: NextPageWithLayout = () => {
  23. const router = useRouter()
  24. const { id, ref, content, skip } = useParams()
  25. const previousRoute = usePrevious(id)
  26. const { data: project } = useSelectedProjectQuery()
  27. const editor = useEditorType()
  28. const tabs = useTabsStateSnapshot()
  29. const snapV2 = useSqlEditorV2StateSnapshot()
  30. const { history, setLastVisitedSnippet } = useDashboardHistory()
  31. const allSnippets = useSnippets(ref!)
  32. const snippet = allSnippets.find((x) => x.id === id)
  33. const tabId = !!id ? tabs.openTabs.find((x) => x.endsWith(id)) : undefined
  34. // [Joshen] May need to investigate separately, but occasionally addSnippet doesnt exist in
  35. // the snapV2 valtio store for some reason hence why the added typeof check here
  36. const canFetchContentBasedOnId = Boolean(
  37. id !== 'new' && typeof snapV2.addSnippet === 'function' && !snippet?.isNotSavedInDatabaseYet
  38. )
  39. const { data, error, isError } = useContentIdQuery(
  40. { projectRef: ref, id },
  41. {
  42. retry: false,
  43. enabled: canFetchContentBasedOnId,
  44. }
  45. )
  46. const snippetMissing =
  47. isError && error.code === 404 && error.message.includes('Content not found')
  48. const invalidId = isError && error.code === 400 && error.message.includes('Invalid uuid')
  49. // [Joshen] Atm we suspect that replication lag is causing this to happen whereby a newly created snippet
  50. // shows the "Unable to find snippet" error which blocks the whole UI
  51. // Am opting to silently swallow this error, since the saves are still going through and we're scoping this behaviour
  52. // behaviour down to a very specific use case too with all these conditionals
  53. // More details: https://github.com/briven/briven/pull/39389
  54. const snippetMissingImmediatelyAfterCreating =
  55. !!snippet && snippetMissing && previousRoute === 'new' && 'isNotSavedInDatabaseYet' in snippet
  56. useEffect(() => {
  57. if (ref && data && project) {
  58. // [Joshen] Check if snippet belongs to the current project
  59. if (!IS_PLATFORM || data.project_id === project.id) {
  60. snapV2.setSnippet(ref, data as unknown as SnippetWithContent)
  61. } else {
  62. setLastVisitedSnippet(undefined)
  63. router.replace(`/project/${ref}/sql/new`)
  64. }
  65. }
  66. // eslint-disable-next-line react-hooks/exhaustive-deps
  67. }, [ref, data, project])
  68. // Load the last visited snippet when landing on /new
  69. useEffect(() => {
  70. if (
  71. id === 'new' &&
  72. skip !== 'true' && // [Joshen] Skip flag implies to skip loading the last visited snippet
  73. history.sql !== undefined &&
  74. content === undefined
  75. ) {
  76. const snippet = allSnippets.find((snippet) => snippet.id === history.sql)
  77. if (snippet !== undefined) router.replace(`/project/${ref}/sql/${history.sql}`)
  78. }
  79. // eslint-disable-next-line react-hooks/exhaustive-deps
  80. }, [id, allSnippets, content])
  81. // Watch for route changes
  82. useEffect(() => {
  83. if (!router.isReady || !id || id === 'new') return
  84. const tabId = createTabId('sql', { id })
  85. const snippet = allSnippets.find((x) => x.id === id)
  86. tabs.addTab({
  87. id: tabId,
  88. type: 'sql',
  89. label: snippet?.name || generateSnippetTitle(),
  90. metadata: {
  91. sqlId: id,
  92. name: snippet?.name,
  93. },
  94. })
  95. // eslint-disable-next-line react-hooks/exhaustive-deps
  96. }, [router.isReady, id])
  97. if ((snippetMissing || invalidId) && !snippetMissingImmediatelyAfterCreating) {
  98. return (
  99. <div className="flex items-center justify-center h-full">
  100. <div className="w-[400px]">
  101. <Admonition
  102. type="default"
  103. title={`Unable to find snippet with ID ${id}`}
  104. description="This snippet doesn't exist in your project"
  105. >
  106. {!!tabId ? (
  107. <Button
  108. type="default"
  109. className="mt-2"
  110. onClick={() => {
  111. tabs.handleTabClose({
  112. id: tabId,
  113. router,
  114. editor,
  115. onClearDashboardHistory: () => setLastVisitedSnippet(undefined),
  116. })
  117. }}
  118. >
  119. Close tab
  120. </Button>
  121. ) : (
  122. <Button
  123. asChild
  124. type="default"
  125. className="mt-2"
  126. onClick={() => setLastVisitedSnippet(undefined)}
  127. >
  128. <Link href={`/project/${ref}/sql`}>Head back</Link>
  129. </Button>
  130. )}
  131. </Admonition>
  132. </div>
  133. </div>
  134. )
  135. }
  136. return <SQLEditor />
  137. }
  138. SqlEditor.getLayout = (page) => (
  139. <DefaultLayout>
  140. <EditorBaseLayout productMenu={<SQLEditorMenu />} product="SQL Editor">
  141. <SQLEditorLayout>{page}</SQLEditorLayout>
  142. </EditorBaseLayout>
  143. </DefaultLayout>
  144. )
  145. export default SqlEditor